
import re
import warnings

# pyfilesystem from http://code.google.com/p/pyfilesystem/
from fs.opener import fsopendir
import fs.path

from pytombo import BaseNotes
from pytombo.tombonote import dos2unix, unix2dos
import pytombo.search
import pytombo.chi_io


class PyFsFileNotes(BaseNotes):
    """PyTombo notes in a PyFilesystem, e.g. zip file
    TODO support encrypted zip files"""
    def __init__(self, note_root):
        """note_root is a file system path that fsopendir() from
        http://code.google.com/p/pyfilesystem/ can open, e.g.:
        
            /tmp
            osfs:///tmp
            zip:///tmp.zip
        
        """
        self.note_root = note_root
        # TODO if self.note_root lowwer ends with .zip prefix with zip://
        #self.note_root = 'zip://' + self.note_root
        self._fs = fsopendir(self.note_root)

    def recurse_notes(self, sub_dir=None, filename_filter=pytombo.search.is_text_or_chi_note_filename_filter):
        notedir = sub_dir or '/'
        notedir = fs.path.abspath(notedir)
        # walkfiles doesn't have a sort option, so walk everything
        for dirpath, filenames in self._fs.walk(notedir, search='depth'):
            filenames.sort()
            for filename in filenames:
                filename = fs.path.join(dirpath, filename)
                if filename_filter(filename):
                    yield pytombo.tombonote.remove_leading_path(notedir, filename)

    def directory_contents(self, dirname=None):
        """Simple non-recursive Tombo note lister.
        Returns tuple (list of directories, list of files) in @dirname"""
        dirname = dirname or '/'
        dirname = fs.path.abspath(dirname)
        
        file_list = []
        dir_list = []
        
        for full_filename in self._fs.listdir(dirname, absolute=True):
            fname = fs.path.basename(full_filename)
            if self._fs.isfile(full_filename):
                if pytombo.search.is_text_or_chi_note_filename_filter(fname):
                    file_list.append(fname)
            else:
                # just assume it is a directory
                dir_list.append(fname)

        dir_list.sort()
        file_list.sort()
        return dir_list, file_list

    def note_contents(self, filename, password=None, dos_newlines=True):
        filename = fs.path.abspath(filename)
        lower_filename = filename.lower()
        if lower_filename.endswith('.txt'):
            fileobj = self._fs.open(filename, 'rb')
            plain_text = fileobj.read()
        elif lower_filename.endswith('.chi'):
            reset_password = False
            if password is None:
                raise pytombo.chi_io.BadPassword('None passed in for password for file %r' % filename)
            while 1:
                if callable(password):
                    note_password = password(filename=filename, reset=reset_password)
                else:
                    # Assume password bytes passed in
                    note_password = password
                if note_password is None:
                    raise pytombo.chi_io.BadPassword('missing or bad password for %s' % filename)
                reset_password = False
                try:
                    fileobj = self._fs.open(filename, 'rb')
                    plain_text = pytombo.chi_io.read_encrypted_file(fileobj, note_password)
                    break
                except pytombo.chi_io.BadPassword, info:
                    ## We will try the file again with a new (reset) password
                    reset_password = True
        else:
            raise pytombo.tombonote.NotSupported('Unknown file type requested for read')
        
        fileobj.close()
        if dos_newlines:
            if '\r' in plain_text:
                plain_text = dos2unix(plain_text)
        return plain_text
    
    def search_iter(self, search_term, search_path, search_term_is_a_regex=True, ignore_case=True, search_encrypted=False, get_password_callback=None, progess_callback=None, findonly_filename=None, index_name=None):
        """Search for `search_term`, returning a generator of:
            tuple of (note_filename, [list of matches, tuple of (line_number, whole_line])
            Example:
                ['somefile.txt', [(2, 'line two'),]]
        
        Three types of "grep" (i.e. not full text search) search to support:

            1)  direct search for this phrase
            2)  regex search
            3)  twist on #1, any of (or all) words -- stretch goal...
        
        index_name - is here only for api compatibility (with full text search). It could be ignored if provided but this is probably a mistake by the caller so raise a warning
        search_path is IGNORED and assumes to be the root of the notedir (could be supported at a later date)
        """
        search_path = '/'
        #print 'DEBUG', (search_term, search_path, search_term_is_a_regex, ignore_case, search_encrypted, get_password_callback, progess_callback, findonly_filename, index_name)
        if index_name:
            warnings.warn('regex/grep text search requested with an index parameter')

        if not search_term_is_a_regex:
            search_term = re.escape(search_term)
        if ignore_case:
            regex_object = re.compile(search_term, re.IGNORECASE)
        else:
            regex_object = re.compile(search_term)
        filename_filter_str = None
        if findonly_filename:
            filename_filter_str = regex_object
        is_note_filename_filter = pytombo.search.note_filename_filter_gen(allow_encrypted=search_encrypted, filename_filter_str=filename_filter_str)
        recurse_notes_func = self.recurse_notes
        for x in recurse_notes_func(search_path, is_note_filename_filter):
            if progess_callback:
                progess_callback(filename=x)
            if filename_filter_str:
                yield (x, [(1, 'FILENAME SEARCH HIT\n')])
            include_contents = True
            include_contents = False
            ## TODO decide what to do with include_contents - default or make a parameter
            if not filename_filter_str or include_contents:
                try:
                    note_text = self.note_contents(x, password=get_password_callback, dos_newlines=True)
                except pytombo.chi_io.BadPassword, info:
                    raise pytombo.search.SearchCancelled(str(info))
                search_res = pytombo.search.grep_string(note_text, regex_object)
                if search_res:
                    yield (x, search_res)

    def note_contents_save(self, notetext, orig_notetext=None, filename=None, password=None, dos_newlines=True, backup=True):
        if filename:
            filename = fs.path.abspath(filename)
        
        orig_notetext = notetext
        if dos_newlines:
            if '\r' in notetext:
                notetext = dos2unix(notetext)
            notetext = unix2dos(notetext)
        #notetext = notetext + 'fail sanity check'

        if filename:
            # TODO need mkdir either here or in pytombo.tombonote.save_file_contents
            lower_filename = filename.lower()
            if lower_filename.endswith('.txt'):
                tmpfile = self._fs.open(filename, 'wb')
                tmpfile.write(notetext)
                tmpfile.close()
            elif lower_filename.endswith('.chi'):
                if get_pass is None:
                    raise pytombo.chi_io.BadPassword('None passed in for password for file %r' % filename)
                password = get_pass  # Assumes get_pass is the password bytes
                return encrypt_file(filename, notetext, password, dos_newlines=dos_newlines)
            else:
                raise pytombo.chi_io.UnsupportedFile('Unknown file type requested for write')
        else:
            raise pytombo.tombonote.NotSupported()
        
        if pytombo.tombonote.DEBUG_CHECK_NOTE:
            saved_text = self.note_contents(filename, password=password, dos_newlines=dos_newlines)
            assert saved_text == orig_notetext, 'note_contents_save() saved text does not match input text'+repr((saved_text, orig_notetext))
        # TODO return???
        
    def note_delete(self, filename, backup=True):
        fullpath_filename = fs.path.abspath(filename)
        if self._fs.exists(fullpath_filename):
            if backup:
                self._fs.rename(fullpath_filename, fullpath_filename + '.bak')
            else:
                self._fs.remove(fullpath_filename)
    
    def note_size(self, filename):
        fullpath_filename = fs.path.abspath(filename)
        return self._fs.getsize(fullpath_filename)
